package org.openfantasia.musica.instruments.jmusic;

import jm.audio.AudioObject;
import jm.audio.Instrument;
import jm.audio.io.SampleOut;
import jm.audio.synth.*;

/**
 * A basic subtractive synthesizer that allows specification of
 * waveform, low pass filter cutoff, and amplitude envelope.
 *
 * @author Andrew Brown
 */

public final class SubtractiveSynthInst extends Instrument {
	//----------------------------------------------
	// Attributes
	//----------------------------------------------

	private int sampleRate;

	private int filterCutoff;

	private int channels = 2;

	private double[] envValues;

	/**
	 * Which waveform to use for the instrument - check the Oscillator class for choices
	 */
	private int waveform;

	/**
	 * The number of milliseconds in the attack phrase of
	 * the amplitude envelope
	 */
	private int attack;

	/**
	 * The number of milliseconds in the decay phrase of
	 * the amplitude envelope
	 */
	private int decay;

	/**
	 * The level of the sustain phrase of
	 * the amplitude envelope, normally between 0.0 and 1.0
	 */
	private double sustain;

	/**
	 * The number of milliseconds in the release phrase of
	 * the amplitude envelope
	 */
	private int release;

	/**
	 * The number of detuned oscillators to run in parralell
	 */
	private int numbOfOsc;

	/**
	 * transpose the second oscillator down one octave?
	 */
	private boolean subOsc;

	//----------------------------------------------
	// Constructor
	//----------------------------------------------


	/**
	 * Basic default constructor to set an initial
	 * sampling rate and otherwise uses default values.
	 *
	 * @param sampleRate
	 */
	public SubtractiveSynthInst(int sampleRate) {
		this(sampleRate, Oscillator.SAWTOOTH_WAVE);
	}


	/**
	 * Constructor that sets sample rate and waveform,
	 * See the jm.audio.synth.Oscillator class for valid waveform values.
	 *
	 * @param sampleRate The number of samples per second (quality)
	 * @param waveform	 The oscillator shape to use.
	 */
	public SubtractiveSynthInst(int sampleRate, int waveform) {
		this(sampleRate, waveform, 500);
	}


	/**
	 * Constructor that sets sample rate, waveform, and low pass filter cutoff.
	 *
	 * @param sampleRate	 The number of samples per second (quality)
	 * @param waveform		 The oscillator shape to use
	 * @param filterCutoff The frequency above which overtones are cut
	 */
	public SubtractiveSynthInst(int sampleRate, int waveform, int filterCutoff) {
		this(sampleRate, waveform, filterCutoff, 10, 10, 0.6, 100);
	}


	/**
	 * Constructor that sets sample rate, waveform, filter cutoff, and envelope.
	 *
	 * @param sampleRate	 The number of samples per second (quality).
	 * @param waveform		 The oscillator shape to use
	 * @param filterCutoff The frequency above which overtones are cut.
	 * @param attack			 An envelope value in milliseconds.
	 * @param decay				An envelope value in milliseconds.
	 * @param sustain			An envelope value for volume, from 0.0 - 1.0.
	 * @param release			An envelope value in milliseconds.
	 */
	public SubtractiveSynthInst(int sampleRate, int waveform, int filterCutoff,
			int attack, int decay, double sustain, int release) {
		this(sampleRate, waveform, filterCutoff, attack, decay, sustain, release, 1);
	}


	/**
	 * Constructor that sets sample rate, waveform, filter cutoff, and envelope.
	 *
	 * @param sampleRate	 The number of samples per second (quality).
	 * @param waveform		 The oscillator shape to use
	 * @param filterCutoff The frequency above which overtones are cut.
	 * @param attack			 An envelope value in milliseconds.
	 * @param decay				An envelope value in milliseconds.
	 * @param sustain			An envelope value for volume, from 0.0 - 1.0.
	 * @param release			An envelope value in milliseconds.
	 * @param numbOfOsc		The number of parallel oscillators to use.
	 */
	public SubtractiveSynthInst(int sampleRate, int waveform, int filterCutoff,
			int attack, int decay, double sustain, int release, int numbOfOsc) {
		this(sampleRate, waveform, filterCutoff, attack, decay, sustain, release, numbOfOsc, false);
	}


	/**
	 * Constructor that sets sample rate, waveform, filter cutoff, and envelope.
	 *
	 * @param sampleRate	 The number of samples per second (quality).
	 * @param waveform		 The oscillator shape to use
	 * @param filterCutoff The frequency above which overtones are cut.
	 * @param attack			 An envelope value in milliseconds.
	 * @param decay				An envelope value in milliseconds.
	 * @param sustain			An envelope value for volume, from 0.0 - 1.0.
	 * @param release			An envelope value in milliseconds.
	 * @param numbOfOsc		The number of parallel oscillators to use.
	 * @param subOsc			 A choice about lowering the second osc one octave.
	 */
	public SubtractiveSynthInst(int sampleRate, int waveform, int filterCutoff,
			int attack, int decay, double sustain, int release, int numbOfOsc, boolean subOsc) {
		this.sampleRate = sampleRate;
		this.waveform = waveform;
		this.filterCutoff = filterCutoff;
		this.envValues = envValues;
		this.attack = attack;
		this.decay = decay;
		this.sustain = sustain;
		this.release = release;
		this.numbOfOsc = numbOfOsc;
		this.subOsc = subOsc;
	}

	//----------------------------------------------
	// Methods
	//----------------------------------------------


	/**
	 * Initialisation method used to build the objects that
	 * this instrument will use and specify thier interconnections.
	 */
	public void createChain() {
		// LFO modulator
		Value modFrequency[] = new Value[numbOfOsc];
		Oscillator modulator[] = new Oscillator[numbOfOsc];
		Oscillator[] oscArray = new Oscillator[numbOfOsc];
		Value constFreq[] = new Value[numbOfOsc];
		Add add[] = new Add[numbOfOsc];
		// volume balance
		Volume[] volArray = new Volume[numbOfOsc];
		// fill
		for (int i = 0; i < numbOfOsc; i++) {
			// modulator
			modFrequency[i] = new Value(this, this.sampleRate, this.channels, (float) (Math.random() * 0.2));
			modulator[i] = new Oscillator(modFrequency[i], Oscillator.SINE_WAVE, Oscillator.FREQUENCY);
			modulator[i].setAmp((float) 0.3);
			// constant
			constFreq[i] = new Value(this, this.sampleRate, this.channels, Value.NOTE_PITCH);
			add[i] = new Add(new AudioObject[]{constFreq[i], modulator[i]});
			// carrier
			oscArray[i] = new Oscillator(add[i], this.waveform, Oscillator.FREQUENCY);
			//oscArray[i] = new Oscillator(this, this.waveform, this.sampleRate, this.channels);
			if (i % 2 == 0 && i > 0) {
				oscArray[i].setFrqRatio((float) (1.0 + 0.001 * (i - 1)));
			}
			else {
				if (this.subOsc && i == 1) {
					oscArray[i].setFrqRatio((float) (0.5 - 0.001 * i));
				}
				else {
					oscArray[i].setFrqRatio((float) (1.0 - 0.001 * i));
				}
			}
			// volume difference between main and other oscillators
			if (i == 0) {
				volArray[i] = new Volume(oscArray[i], 1.0);
			}
			else {
				volArray[i] = new Volume(oscArray[i], 0.3);
			}

		}
		Add add2 = new Add(volArray);
		Filter filt = new Filter(add2, this.filterCutoff, Filter.LOW_PASS);
		ADSR env = new ADSR(filt, attack, decay, sustain, release);
		//Envelope env = new Envelope(filt, new double[] {0.0, 0.0, 0.1, 1.0, 1.0, 0.0});
		Volume vol = new Volume(env);
		StereoPan pan = new StereoPan(vol);
		SampleOut sout = new SampleOut(pan);
	}
}

